package org.phalanx.server.game;

import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;

import org.phalanx.game.units.AbstractUnit;
import org.phalanx.game.units.ArmyEncampmentUnit;
import org.phalanx.game.units.DestroyerShipUnit;
import org.phalanx.game.units.GenericUnit;
import org.phalanx.game.units.HeavyScoutUnit;
import org.phalanx.game.units.HeavySeigeShipUnit;
import org.phalanx.game.units.InfantryUnit;
import org.phalanx.game.units.LightScoutUnit;
import org.phalanx.game.units.MountedUnit;
import org.phalanx.game.units.RoyalInfantryUnit;
import org.phalanx.game.units.RoyalMountedUnit;
import org.phalanx.game.units.VillageUnit;
import org.phalanx.map.MapCoordinates;
import org.phalanx.map.PhalanxMap;
import org.phalanx.map.PhalanxMapTerrain;
import org.phalanx.server.PhalanxServer;
import org.phalanx.server.ServerCache.CacheManager;
import org.phalanx.server.ServerCache.Cacheable;

public class PhalanxBattleRPGEngine implements Cacheable {
	private Integer gameId;
	private Hashtable<String,AbstractUnit> unitTable;
	private Hashtable<Integer,MapCoordinates> buildPointTable;
	private PhalanxMap gameMap;
	private Random r;
	private int count = 0;

	public static final String INVALID_MOVE = "invalidmove";
	public static final String INVALID_ATTACK = "invalidattack";
	public static final String OUT_OF_BOUNDS = "outofbounds";
	public static final String SQUARE_FULL = "squarefull";
	public static final String UNIT_NOT_FOUND = "unitnofound";
	public static final String MOVE_OK = "moveok";

	public static final String BUILD_ATTR = "BUILD";
	public static final String TRAIN_ATTR = "TRAIN";
	public static final String POSITION = "POSITION";
	public static final String FORMATION_ATTR = "FORMATION";
	public static final String BOARDING_SHIP = "BOARDING";

	public static final Integer ECONOMY_PER_TURN = 500;

	public PhalanxBattleRPGEngine(Integer gameId, PhalanxMap gameMap){
		setGameId(gameId);
		this.setGameMap(gameMap);
		initializeGameTypeTable();
	}

	public void setGameId(Integer gameId) {
		this.gameId = gameId;
	}

	public Integer getGameId() {
		return gameId;
	}

	private void initializeGameTypeTable(){
		unitTable = new Hashtable<String, AbstractUnit>();

		unitTable.put(new VillageUnit().getUnitName(), new VillageUnit());
		unitTable.put(new RoyalMountedUnit().getUnitName(), new RoyalMountedUnit());
		unitTable.put(new RoyalInfantryUnit().getUnitName(), new RoyalInfantryUnit());
		unitTable.put(new MountedUnit().getUnitName(), new MountedUnit());
		unitTable.put(new LightScoutUnit().getUnitName(), new LightScoutUnit());
		unitTable.put(new InfantryUnit().getUnitName(), new InfantryUnit());
		unitTable.put(new HeavySeigeShipUnit().getUnitName(), new HeavySeigeShipUnit());
		unitTable.put(new HeavyScoutUnit().getUnitName(), new HeavyScoutUnit());
		unitTable.put(new DestroyerShipUnit().getUnitName(), new DestroyerShipUnit());
		unitTable.put(new ArmyEncampmentUnit().getUnitName(), new ArmyEncampmentUnit());
	}

	public String checkMoveSanity(GameMove gMove){
		String statusMessage = INVALID_MOVE;
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		PhalanxGameSettings settings = (PhalanxGameSettings)cacheMgr.getElement("GameListCache", gameId);
		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);

		if (settings != null && gState != null){
			Integer gameTurn = settings.getCurrentTurn();

			// Check if MapCoordinates are in the bounds of the map

			if (gMove.getActionType().equals(GameMoveQueue.ATTACK)){
				if (isCoordinatesInBounds(gMove.getCoordinates1()) && isCoordinatesInBounds(gMove.getCoordinates2())){
					PhalanxUnitState unit1 = gState.getUnitTypeById(gMove.getUnitid1(), gameTurn);
					PhalanxUnitState unit2 = gState.getUnitTypeById(gMove.getUnitid2(), gameTurn);

					if (unit1 != null && unit2 != null){
						if (!isPreviousMoveVoid(unit1.getUnitId(), gameTurn, GameMoveQueue.ATTACK)){
							if (unit1.getMapCoordinates().equals(gMove.getCoordinates1()) && 
									unit2.getMapCoordinates().equals(gMove.getCoordinates2())){
								// They must be enemies!!!
								if (unit1.getUserId() != unit2.getUserId()){
									// If the units are where the moves say they are according to the game state
									if (checkUnitOnCorrectTerrain(unit1, null) && checkUnitOnCorrectTerrain(unit2, null) &&
											checkIfMoveIsAdjacent(gMove.getCoordinates1(), gMove.getCoordinates2())){
										statusMessage = MOVE_OK;
									}
								}
							}
						} 
					}// unit1 != null and unit2 != null
				}
			} else if (gMove.getActionType().equals(GameMoveQueue.ATTRIBUTES)){
				statusMessage = checkAttributeMoveSanity(gMove);
			} else if (gMove.getActionType().equals(GameMoveQueue.TRAVEL)){
				if (isCoordinatesInBounds(gMove.getCoordinates1()) && isCoordinatesInBounds(gMove.getCoordinates2())){
					PhalanxUnitState unit1 = gState.getUnitTypeById(gMove.getUnitid1(), gameTurn);
					PhalanxUnitState unit2 = gState.getUnitTypeById(gMove.getUnitid2(), gameTurn);

					// Units can only occupy the same square with their own team
					if (!isPreviousMoveVoid(unit1.getUnitId(), gameTurn, GameMoveQueue.TRAVEL)){
						if (unit2 == null || unit1.getUserId() == unit2.getUserId()){
							if (unit1.getMapCoordinates().equals(gMove.getCoordinates1())){
								if (checkUnitOnCorrectTerrain(unit1, null) && 
										checkUnitOnCorrectTerrain(unit1, gMove.getCoordinates2()) &&
										checkIfMoveIsAdjacent(gMove.getCoordinates1(), gMove.getCoordinates2())){
									GenericUnit gUnit = (GenericUnit)unitTable.get(unit1.getPhalanxUnit());

									if (gUnit.getUnitAttribute(AbstractUnit.IS_MOBILE).getAttributeAsBoolean()){
										if (gUnit.getUnitAttribute(AbstractUnit.SEA_VESSEL) != null){
											if (getPreviousMoveCount(unit1.getUnitId(), gameTurn) < 6){
												statusMessage = MOVE_OK;										
											}
										} else if (gUnit.getUnitAttribute(AbstractUnit.SEA_VESSEL) == null){
											if (getPreviousMoveCount(unit1.getUnitId(), gameTurn) <= 1){
												statusMessage = MOVE_OK;
											}
										}
									}

								}
							}
						} // unit2 == null
					}
				}
			}


			//			statusMessage = MOVE_OK;
		}

		return statusMessage;
	}

	private String checkAttributeMoveSanity(GameMove gMove){
		String statusMessage = INVALID_MOVE;

		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		PhalanxGameSettings settings = (PhalanxGameSettings)cacheMgr.getElement("GameListCache", gameId);
		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);

		Integer gameTurn = settings.getCurrentTurn();

		if (isCoordinatesInBounds(gMove.getCoordinates1())){
			PhalanxUnitState unit1 = gState.getUnitTypeById(gMove.getUnitid1(), gameTurn);

			if (unit1.getMapCoordinates().equals(gMove.getCoordinates1())){
				if (!checkForDuplicateAttributes(unit1.getUnitId(), gameTurn, gMove)){
					if (gMove.getAttributeName() != null && gMove.getAttributeValue() != null){
						if (unit1.getCurrentState() == null){
							GenericUnit unitSettings = (GenericUnit)unitTable.get(unit1.getPhalanxUnit());
							if (unitSettings.getUnitAttribute(AbstractUnit.IS_MOBILE).getAttributeAsBoolean() &&
									unitSettings.getUnitAttribute(AbstractUnit.SEA_VESSEL) == null){
								// Mobile land unit building a village
								if (gMove.getAttributeName().equals(BUILD_ATTR)){
									GenericUnit building = (GenericUnit)unitTable.get(gMove.getAttributeValue());

									if (building != null){
										if (!building.getUnitAttribute(AbstractUnit.IS_MOBILE).getAttributeAsBoolean()){
											statusMessage = MOVE_OK;
										}
									}
								}

							}
							else if (!unitSettings.getUnitAttribute(AbstractUnit.IS_MOBILE).getAttributeAsBoolean() 
									&& unitSettings.getUnitName().equals(new VillageUnit().getUnitName())){
								if (gMove.getAttributeName().equals(TRAIN_ATTR)){
									GenericUnit training = (GenericUnit)unitTable.get(gMove.getAttributeValue());

									if (unit1.getWealth() != null && 
											unit1.getWealth() > training.getUnitAttribute(AbstractUnit.COST).
											getAttributeAsInteger()){
										statusMessage = MOVE_OK;
									}
								}
							}
						}
						else if (unit1.getCurrentState() != null){
							String[] getCurrentUnitStatus = unit1.getCurrentState().split(":");
							if (getCurrentUnitStatus.length > 0){
								if (!getCurrentUnitStatus[0].equals(gMove.getAttributeName())){
									statusMessage = MOVE_OK;
								}
							}
						}
					}
				}
			}
		}
		//		if (checkForDuplicateAttributes(gameMove, currentTurn, gMove))

		return statusMessage;
	}

	private boolean checkForDuplicateAttributes(Integer unitId, Integer currentTurn, GameMove gMove){
		boolean isDupe = false;
		int dupeCount = -1;

		// Users can apply each attribute of a unit once per turn
		// For example a village can only train a unit once per turn
		// Or a army can set a formation once per turn
		// Any dupes will reject this new gamemove

		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);

		if (gQueue != null){
			Iterator<GameMove> unitMoves = gQueue.getMoveByAttackType(currentTurn, GameMoveQueue.ATTRIBUTES).iterator();

			while (unitMoves.hasNext()){
				GameMove possibleDupe = unitMoves.next();

				if (possibleDupe.getUnitid1().equals(unitId)){
					if (possibleDupe.getAttributeName().equals(gMove.getAttributeName())){
						dupeCount++;
					}
				}
			}

			isDupe = dupeCount > 0; //It will always count its own move of course so we have to count 2 dupes
		}

		return isDupe;
	}

	private boolean isPreviousMoveVoid(Integer unitId, Integer currentTurn, Integer attackOrMove){
		// In Phalanx units can either attack or move but cannot do both with the same unit in the same turn
		// This function checks to see if a move is invoked whether an attack has already been invoked
		// and vice versa

		boolean isVoid = false;
		Integer offendingMove = attackOrMove.equals(GameMoveQueue.ATTACK) ? GameMoveQueue.TRAVEL : GameMoveQueue.ATTACK;

		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);

		if (gQueue != null){
			Iterator<GameMove> unitMoves = gQueue.getMoveByAttackType(currentTurn, offendingMove).iterator();

			while (unitMoves.hasNext() && !isVoid){
				GameMove possibleViolation = unitMoves.next();

				if (possibleViolation.getUnitid1().equals(unitId)){
					isVoid = true;
				}
			}
		}


		return isVoid;
	}

	private Integer getPreviousMoveCount(Integer unitId, Integer currentTurn){
		// Land units can only move 1 square
		// while sea units can move 5

		// If a land unit returns 1 the other moves should be voided
		// If a sea unit returns 5 the sea unit moves should be voided
		Integer moveCount = 0;

		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);

		if (gQueue != null){
			Iterator<GameMove> i = gQueue.getMoveByAttackType(currentTurn, GameMoveQueue.TRAVEL).iterator();

			while (i.hasNext()){
				GameMove oneMove = i.next();

				if (oneMove.getUnitid1().equals(unitId)){
					moveCount++;
				}
			}
		}

		return moveCount;
	}

	private boolean isCoordinatesInBounds(MapCoordinates c){
		return (c.getX() < gameMap.getMapWidth() && c.getY() < gameMap.getMapLength() && c.getX() >= 0 && c.getY() >= 0);
	}

	private boolean checkIfMoveIsAdjacent(MapCoordinates c1, MapCoordinates c2){
		return Math.abs(c2.getX() - c1.getX()) <= 1 && Math.abs(c2.getY() - c1.getY()) <= 1;
	}

	private boolean checkUnitOnCorrectTerrain(PhalanxUnitState uState, MapCoordinates c1){
		boolean isCorrect = false;
		MapCoordinates c;

		if (c1 == null){
			c = uState.getMapCoordinates();
		} else {
			c = c1;
		}

		String terrainType = gameMap.getMapTileAt(c).getTerrain().getTerrainType();

		GenericUnit gameUnit = (GenericUnit)unitTable.get(uState.getPhalanxUnit());

		// Ships can only be in water, land units on land (except transport)

		if (terrainType.equals(PhalanxMapTerrain.TERRAIN_WATER)){
			if (gameUnit.getUnitAttribute(AbstractUnit.SEA_VESSEL) != null){
				isCorrect = true;
			}else if (gameUnit.getUnitAttribute(AbstractUnit.SEA_VESSEL) == null && 
					uState.getCurrentState() != null && uState.getCurrentState().equals(BOARDING_SHIP)){
				isCorrect = true;
			}
		} else if (!terrainType.equals(PhalanxMapTerrain.TERRAIN_WATER)){
			if (gameUnit.getUnitAttribute(AbstractUnit.SEA_VESSEL) == null 
			/*TODO: && !terrainType.equals(PhalanxMapTerrain.TERRAIN_IMPASSIBLE)*/){
				isCorrect = true;
			}
		}

		return isCorrect;
	}

	public void calculateGameState(){
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		PhalanxGameSettings settings = (PhalanxGameSettings)cacheMgr.getElement("GameListCache", gameId);
		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);
		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);


		if (settings != null && gState != null){
			Integer currentTurn = settings.getCurrentTurn();			

			cloneGameStateForNextTurn(gState, currentTurn);
			calculateGameStateSpecial();
			if (gQueue != null){
				calculateGameStateAttack(currentTurn);
				calculateGameStateAttribute(currentTurn);
				calculateGameStateTravel(currentTurn);
			}
		}

		settings.setCurrentTurn(settings.getCurrentTurn()+1);
		cacheMgr.setElement("GameListCache", gameId, settings);

		cacheMgr.setElement("CurrentGameState", gameId, gState);
	}

	private void calculateGameStateAttack(Integer currentTurn){
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();


		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);
		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);


		if (gState != null){
			Iterator<GameMove> i = gQueue.getMoveByAttackType(currentTurn, GameMoveQueue.ATTACK).iterator();

			while(i.hasNext()){
				GameMove oneMove = i.next();

				if (checkMoveSanity(oneMove).equals(MOVE_OK)){
					if (oneMove.getActionType().equals(GameMoveQueue.ATTACK)){
						PhalanxUnitState unit1 = gState.getUnitTypeById(oneMove.getUnitid1(), currentTurn + 1);
						PhalanxUnitState unit2 = gState.getUnitTypeById(oneMove.getUnitid2(), currentTurn + 1);

						getAttackOutcome(unit1, unit2); // Initiated Attack
						getAttackOutcome(unit2, unit1); // Automatic fight back

						if (unit1.getHitPoints() < 1){
							gState.removeUnitState(unit1);
						}
						if (unit2.getHitPoints() < 1){
							gState.removeUnitState(unit2);
						}
						// Battle it out
					}
				}
			} // While
		}
	}

	private void calculateGameStateAttribute(Integer currentTurn){
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);
		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);

		if (gState != null){
			Iterator<GameMove> i = gQueue.getMoveByAttackType(currentTurn, GameMoveQueue.ATTRIBUTES).iterator();

			while (i.hasNext()){
				GameMove oneMove = i.next();

				if (checkMoveSanity(oneMove).equals(MOVE_OK)){

					PhalanxUnitState unit = gState.getUnitTypeById(oneMove.getUnitid1(), currentTurn + 1);

					if (oneMove.getAttributeName().equals(BUILD_ATTR)){
						// No charge, just your army unit!
						unit.setCurrentState(BUILD_ATTR + ":" + oneMove.getAttributeValue() + ":1");
					}
					else if (oneMove.getAttributeName().equals(TRAIN_ATTR)){
						unit.setWealth(unit.getWealth() - getCostOfUnit(oneMove.getAttributeValue()));
						unit.setCurrentState(TRAIN_ATTR + ":" + oneMove.getAttributeValue() + ":1");
					} // TODO: Apply position changing
				}
			}
		}
	}

	private Integer getCostOfUnit(String unitName){
		Integer totalCost = 0;

		GenericUnit gUnit = (GenericUnit) unitTable.get(unitName);

		if (gUnit != null){
			totalCost = gUnit.getUnitAttribute(AbstractUnit.COST) == null ? 0 : 
				gUnit.getUnitAttribute(AbstractUnit.COST).getAttributeAsInteger();
		}

		return totalCost;
	}

	private void calculateGameStateTravel(Integer currentTurn){
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);
		GameMoveQueue gQueue = (GameMoveQueue)cacheMgr.getElement("PlayerMoveCache", gameId);

		if (gState != null){

			Iterator<GameMove> i = gQueue.getMoveByAttackType(currentTurn, GameMoveQueue.TRAVEL).iterator();

			while (i.hasNext()){
				GameMove oneMove = i.next();

				if (checkMoveSanity(oneMove).equals(MOVE_OK)){
					if (oneMove.getActionType().equals(GameMoveQueue.TRAVEL)){
						// Check if boarding a ship
						PhalanxUnitState unit1 = gState.getUnitTypeById(oneMove.getUnitid1(), currentTurn + 1);
						String isBoardingShip = oneMove.getAttributeName();

						if (isBoardingShip != null){
							PhalanxUnitState unit2 = gState.getUnitTypeById(oneMove.getUnitid2(), currentTurn + 1);
							
							GenericUnit gUnit2 = (GenericUnit)unitTable.get(unit2.getPhalanxUnit());
							
							// Check if travel is a ship boarding movement
							if (gUnit2 != null && gUnit2.getUnitAttribute(AbstractUnit.SEA_VESSEL) != null
									&& unit1.getCurrentState() != null && unit1.getCurrentState().equalsIgnoreCase(BOARDING_SHIP)){
								// Check if vessel is full or not
								
								String boardingStatus = unit2.getCurrentState();
								
								if (boardingStatus != null){
									String[] boardingArray = boardingStatus.split(":");
									
									if (boardingArray.length == 3){
										Integer unitCapacity = gUnit2.getUnitAttribute(AbstractUnit.UNIT_CAPACITY).getAttributeAsInteger();
										if (boardingArray[0].equals(BOARDING_SHIP)){
											Integer unitPayLoad = Integer.valueOf(boardingArray[2]);
											
											if (unitPayLoad < unitCapacity){
												// Board unit
												unit1.setMapCoordinates(oneMove.getCoordinates2());
												unit1.setCurrentState(BOARDING_SHIP);
												unit2.setCurrentState(renderCurrentState(BOARDING_SHIP, "UNIT", 
														new Integer(unitPayLoad + 1).toString()));
											}
										}
									}
								} else {
									// Board unit
									unit1.setMapCoordinates(oneMove.getCoordinates2());
									unit1.setCurrentState(BOARDING_SHIP);
									unit2.setCurrentState(renderCurrentState(BOARDING_SHIP, "UNIT", 
											new Integer(1).toString()));
								}
							}
						} else {  // Plain move
							unit1.setMapCoordinates(oneMove.getCoordinates2());
						}
					}
				}
			}
		}
	}
	
	private String renderCurrentState(String stateName, String subject, String value){
		return new String(stateName + ":" + subject + ":" + value);
	}

	private void calculateGameStateSpecial(){
		// This area is dedicated to all automatic and progressive game events get processed

		// For example: Each village producing gold each turn or a building getting produced

		// TODO: Add more complex rules. For now every fully built village produces wealth

		// Calculate the economy

		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);

		Iterator<PhalanxUnitState> i = getPlayerUnitsOfType(new VillageUnit().getUnitName()).iterator();
		boolean payCheck = true;
		while (i.hasNext()){
			PhalanxUnitState oneVillage = i.next();

			// Check if the village is already built

			String currentStatus = oneVillage.getCurrentState();

			if (currentStatus != null){
				String[] statusArray = currentStatus.split(":");

				if (statusArray.length > 1 && !statusArray[0].equals(BUILD_ATTR)){
					payCheck = false;
				}
			}

			if (payCheck){
				// TODO: Make these attributes more flexible in the future
				Integer wealthPlusWages = (oneVillage.getWealth() == null ? 0 : oneVillage.getWealth()) + 200;
				oneVillage.setWealth(wealthPlusWages);
			}
			payCheck = true;
		}

		// Now cycle through building and training
		// For now building takes 4 turns while training takes 2 for infantry

		// TODO: Expand this to other units and buildings
		cycleThroughInProgressItems();
	}

	private void cycleThroughInProgressItems(){
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
		PhalanxGameSettings settings = (PhalanxGameSettings)cacheMgr.getElement("GameListCache", gameId);
		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);
		Integer newTurn = settings.getCurrentTurn() + 1;

		Iterator<PhalanxUnitState> i = gState.getUnitStateForCurrentTurn(newTurn).iterator();

		while (i.hasNext()){
			PhalanxUnitState inProgressUnit = i.next();

			if (inProgressUnit.getCurrentState() != null){
				String[] currentStateArray = inProgressUnit.getCurrentState().split(":");

				if (currentStateArray.length > 1){
					String buildingOrTraining = currentStateArray[0];
					Integer cycleNumber = Integer.valueOf(currentStateArray[2]);

					if (buildingOrTraining.equals(BUILD_ATTR)){
						String buildingType = currentStateArray[1];

						if (buildingType.equals(new VillageUnit().getUnitName())){
							if (cycleNumber >= 3){
								inProgressUnit.setCurrentState(null);
								applyUnitPropertiesToUnitState(inProgressUnit, new VillageUnit().getUnitName());
								inProgressUnit.setWealth(0);
							} else {
								inProgressUnit.setCurrentState(BUILD_ATTR + ":" + buildingType + ":" +  ++cycleNumber); 
							}
						}
					} else if (buildingOrTraining.equals(TRAIN_ATTR)){
						String trainingType = currentStateArray[1];

						if (cycleNumber >= 2){ // Takes 3 (2 turns cycle wait) turns to train a unit
							// New unit has been spawned
							PhalanxUnitState newUnit = new PhalanxUnitState();
							newUnit.setUnitId(gState.generateUnitStateId(newTurn));
							applyUnitPropertiesToUnitState(newUnit, trainingType);
							newUnit.setMapCoordinates(new MapCoordinates(inProgressUnit.getMapCoordinates().getX(),
									inProgressUnit.getMapCoordinates().getY()));
							newUnit.setExpLevel(0);
							newUnit.setFormation(0);
							newUnit.setPosition(3); // TODO: calculate position
							newUnit.setUserId(inProgressUnit.getUserId());
							newUnit.setTurnId(newTurn);

							gState.addUnitState(newUnit);
						} else {
							inProgressUnit.setCurrentState(BUILD_ATTR + ":" + trainingType + ":" +  ++cycleNumber); 
						}
					}
				}
			}
		}
	}

	private void applyUnitPropertiesToUnitState(PhalanxUnitState currentState, String unitName){
		GenericUnit unitProps = (GenericUnit)unitTable.get(unitName);

		if (unitProps != null){
			currentState.setPhalanxUnit(unitName);
			currentState.setHitPoints(unitProps.getUnitAttribute(AbstractUnit.HIT_POINTS).getAttributeAsInteger());
		}
	}

	private ArrayList<PhalanxUnitState> getPlayerUnitsOfType(String unitType){
		ArrayList<PhalanxUnitState> unitCollection = new ArrayList<PhalanxUnitState>();

		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
		PhalanxGameSettings settings = (PhalanxGameSettings)cacheMgr.getElement("GameListCache", gameId);
		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);

		Iterator<PhalanxUnitState> i = gState.getUnitStateForCurrentTurn(settings.getCurrentTurn() + 1).iterator();

		while (i.hasNext()){
			PhalanxUnitState oneUnit = i.next();

			if (oneUnit.getPhalanxUnit().equals(unitType)){
				unitCollection.add(oneUnit);
			}
		}

		return unitCollection;
	}

	private void cloneGameStateForNextTurn(GameState gState, Integer turnId){
		Iterator<PhalanxUnitState> previousTurn =  gState.getUnitStateForCurrentTurn(turnId).iterator();

		while (previousTurn.hasNext()){
			PhalanxUnitState oneTurn = previousTurn.next();
			PhalanxUnitState clonedTurn = new PhalanxUnitState();

			clonedTurn.setTurnId(turnId + 1);
			clonedTurn.setCurrentState(oneTurn.getCurrentState() == null ? null : oneTurn.getCurrentState());
			clonedTurn.setExpLevel(oneTurn.getExpLevel().intValue());
			clonedTurn.setFormation(oneTurn.getFormation().intValue());
			clonedTurn.setHitPoints(oneTurn.getHitPoints().intValue());
			MapCoordinates c = new MapCoordinates(oneTurn.getMapCoordinates().getX(), oneTurn.getMapCoordinates().getY());

			clonedTurn.setMapCoordinates(c);
			clonedTurn.setPhalanxUnit(new String(oneTurn.getPhalanxUnit()));
			clonedTurn.setPosition(oneTurn.getPosition().intValue());
			clonedTurn.setUnitId(oneTurn.getUnitId().intValue());
			clonedTurn.setUserId(oneTurn.getUserId());
			clonedTurn.setWealth(oneTurn.getWealth() == null ? null : oneTurn.getWealth().intValue());

			gState.addUnitState(clonedTurn);
		}
	}

	private Integer rollDiceSpread(Integer minimum, Integer maximum){
		if (r == null || count == 20){
			CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
			Long seeder = new Date().getTime();
			seeder += cacheMgr.getCache("UserCache").getInternalCacheTable().size();
			seeder += cacheMgr.getCache("UserWebFeedCache").getInternalCacheTable().size();
			seeder += cacheMgr.getCache("GameListCache").getInternalCacheTable().size();

			r = new Random(seeder);
		}
		count++;
		return (r.nextInt() % (maximum - minimum)) + minimum;
	}

	private void getAttackOutcome(PhalanxUnitState attacker, PhalanxUnitState attacked){
		GenericUnit unit1 = (GenericUnit)unitTable.get(attacker.getPhalanxUnit());
		GenericUnit unit2 = (GenericUnit)unitTable.get(attacked.getPhalanxUnit());

		Integer attackDamage = unit1.getUnitAttribute(AbstractUnit.ATTACK).getAttributeAsInteger() - rollDiceSpread(0, 10);
		attackDamage *= attacker.getFormation() == 0 ? 
				unit1.getUnitAttribute(AbstractUnit.ATTACK_FORMATION_BONUS).getAttributeAsDouble() : 1;

				Integer defense = unit2.getUnitAttribute(AbstractUnit.DEFENSE).getAttributeAsInteger() - rollDiceSpread(0, 10);
				defense *= attacked.getFormation() == 1 ?
						unit2.getUnitAttribute(AbstractUnit.DEFENSE_FORMATION_BONUS).getAttributeAsDouble() : 1;

						attacked.setHitPoints(attacked.getHitPoints() - attackDamage + defense);
	}

	public ArrayList<PhalanxUnitState> getVisibleUnits(Integer userId, Integer turnId){
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();
		ArrayList<PhalanxUnitState> unitStateList = new ArrayList<PhalanxUnitState>();

		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);

		Iterator<PhalanxUnitState> i = gState.getUnitStateForCurrentTurn(turnId).iterator();
		ArrayList<MapCoordinates> fovList = this.getTotalFieldOfVision(userId, turnId);

		while(i.hasNext()){
			PhalanxUnitState oneUnit = i.next();

			if (oneUnit.getUserId().equals(userId) || 
					getIsVisible(oneUnit.getMapCoordinates(), fovList) || oneUnit.getPhalanxUnit().equals(new VillageUnit().getUnitName())){
				if (!unitStateList.contains(oneUnit)){
					unitStateList.add(oneUnit);
				}
			}
		}

		return unitStateList;
	}

	private boolean getIsVisible(MapCoordinates c, ArrayList<MapCoordinates> fieldOfVision){
		boolean isVisible = false;

		Iterator<MapCoordinates> i = fieldOfVision.iterator();

		while(i.hasNext() && !isVisible){
			MapCoordinates newCoor = i.next();
			isVisible = newCoor.equals(c);
		}

		return isVisible;
	}

	public ArrayList<MapCoordinates> getTotalFieldOfVision(Integer userId, Integer turnId){
		ArrayList<MapCoordinates> fieldOfVision = new ArrayList<MapCoordinates>();
		CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

		GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);

		if (gState != null){
			Iterator<PhalanxUnitState> totalMap = gState.getUnitStateForCurrentTurn(turnId).iterator();

			while(totalMap.hasNext()){
				PhalanxUnitState currentUnitState = totalMap.next();

				if (currentUnitState.getUserId().equals(userId)){
					GenericUnit currentGameUnit = (GenericUnit)unitTable.get(currentUnitState.getPhalanxUnit());

					Integer fov = currentGameUnit.getUnitAttribute(AbstractUnit.VISION_RADIUS).getAttributeAsInteger();
					Integer xPos = currentUnitState.getMapCoordinates().getX();
					Integer yPos = currentUnitState.getMapCoordinates().getY();

					// All units have a FOV of at least 1, mandatory
					for (int i = xPos - 1; i <= xPos + 1; i++){
						for (int j = yPos - 1; j <= yPos + 1; j++){
							if (!fieldOfVision.contains(new MapCoordinates(i,j))){
								fieldOfVision.add(new MapCoordinates(i,j));
							}
						}
					}

					if (fov > 1){
						for (int fovCount = fov; fovCount > 1; fovCount--){
							for (int i = xPos + fovCount, j = yPos; i >= xPos; i--){
								MapCoordinates c1 = new MapCoordinates(i,j);
								if (!fieldOfVision.contains(c1)){
									fieldOfVision.add(c1);
								}
								MapCoordinates c2 = new MapCoordinates(i-fovCount, j+fovCount);
								if (!fieldOfVision.contains(c2)){
									fieldOfVision.add(c2);
								}
								//								System.out.println("c1 x=" + c1.getX() + " y=" + c1.getY());
								//								System.out.println("c2 x=" + c2.getX() + " y=" + c2.getY());

								j--;
							}

							for (int i = xPos, j = yPos - fovCount; i >= xPos - fovCount; i--){
								MapCoordinates c3 = new MapCoordinates(i,j);
								if (!fieldOfVision.contains(c3)){
									fieldOfVision.add(c3);
								}

								MapCoordinates c4 = new MapCoordinates(i+fovCount, j+fovCount);
								if (!fieldOfVision.contains(c4)){
									fieldOfVision.add(c4);
								}
								//								System.out.println("c3 x=" + c3.getX() + " y=" + c3.getY());
								//								System.out.println("c4 x=" + c4.getX() + " y=" + c4.getY());
								j++;
							}
						}
					} // Fov > 1
				}
			} // While

			Iterator<MapCoordinates> fovList = fieldOfVision.iterator();
			ArrayList<MapCoordinates> invalidCoordinates = new ArrayList<MapCoordinates>();

			while (fovList.hasNext()){
				MapCoordinates c = fovList.next();

				if (!isCoordinatesInBounds(c)){
					invalidCoordinates.add(c);
				}
			}

			fieldOfVision.removeAll(invalidCoordinates);
		}


		return fieldOfVision;
	}

	public void setPlayerOnBuildPoint(MapCoordinates c, Integer uId){
		if (gameMap.getBuildPoints().contains(c)){
			buildPointTable.put(uId, c);
		}
	}

	public void setInitialTurnConditions(){
		// Set players on specified build points

		Enumeration<Integer> e = buildPointTable.keys();

		while (e.hasMoreElements()){
			Integer onePlayer = e.nextElement();
			MapCoordinates bpCoor = buildPointTable.get(onePlayer);


			// Create village unit and infantry unit as default
			CacheManager cacheMgr = PhalanxServer.getServerInstance().getCacheManager();

			GameState gState = (GameState)cacheMgr.getElement("CurrentGameState", gameId);
			VillageUnit village = new VillageUnit();
			InfantryUnit infantry = new InfantryUnit();

			PhalanxUnitState villageUnit = new PhalanxUnitState();
			villageUnit.setTurnId(0);
			villageUnit.setExpLevel(0);
			villageUnit.setMapCoordinates(bpCoor);
			villageUnit.setUserId(onePlayer);
			villageUnit.setPhalanxUnit(village.getUnitName());
			villageUnit.setPosition(0);
			villageUnit.setUnitId(gState.generateUnitStateId(0));
			villageUnit.setHitPoints(village.getUnitAttribute(AbstractUnit.HIT_POINTS).getAttributeAsInteger());
			villageUnit.setWealth(500);

			PhalanxUnitState infantryUnit = new PhalanxUnitState();
			infantryUnit.setTurnId(0);
			infantryUnit.setExpLevel(0);
			infantryUnit.setMapCoordinates(bpCoor);
			infantryUnit.setUserId(onePlayer);
			infantryUnit.setPhalanxUnit(infantry.getUnitName());
			infantryUnit.setPosition(1);
			infantryUnit.setUnitId(gState.generateUnitStateId(0));
			infantryUnit.setHitPoints(infantry.getUnitAttribute(AbstractUnit.HIT_POINTS).getAttributeAsInteger());

			gState.addUnitState(villageUnit);
			gState.addUnitState(infantryUnit);
		}
	}

	public void setGameMap(PhalanxMap gameMap) {
		this.gameMap = gameMap;
	}

	public PhalanxMap getGameMap() {
		return gameMap;
	}

	@Override
	public Integer getId() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setId(Integer id) {
		// TODO Auto-generated method stub

	}

}
